#!/usr/bin/python
########################################################################
# wickddrss - WickDD RSS Generator.  Reads a specified path and creates
#	an RSS file pointing to all files within the path.
#
# Uses PyRSS2Gen (http://www.dalkescientific.com/Python/PyRSS2Gen.html)
#
# Created 2006.04.06 by Rick Barrett
#
# Copyright (c) 2006 by Pixel Corps.  This program may be used under
# the terms of the X11 license, a copy of which should have accompanied
# this program, in the LICENSE file.
########################################################################

from types import *
from path import path
from fnmatch import fnmatch

import sys
import os
import getopt
import datetime
import zipfile
import sha
import PyRSS2Gen
import mimetypes

## Global variables
gInputPath = None			# -i argument
gOutputPath = None			# -o argument
gTorrentTracker = None  	# -r argument
gTorrentGen = None			# -t argument
gBaseURL = None				# -u argument
gRSSTitle = None			# -T argument
gRSSDesc = None
gRecurse = False			# -r argument
gIncludeFiles = []			# args - files
gExcludeFiles = ['.*']		# -x argument
gSort = None				# -S argument
gSortDir = 1				# -R argument
gLimitFiles = None			# -L argument


def usage():
	"""Print the program's usage message"""

	print """
usage: wickddrss [-i path-to-index] [-o rss-output-path]
        [-u base-url] [-T rss-title]

    -h  Help (show usage info)
    -i  Path to index for rss
    -o  Location to write rss file (defaults to directory above -i)
    -r  Recurse subdirectories
    -u  Link URL for specified index path
    -v  Verbose (display options)
    -T  RSS Title
    -D  RSS Description
    -S  Sort files (MDATE, CDATE, SIZE)
    -R  Reverse Sort
    -L  Limit files (list N files only)
    -I  Include Files in indexing
    -X  Exclude files from indexing

    When specifying files to {in,ex}clude, you may use * and ? wildcards
    
    You must specify -i and -u. All other flags are optional.
"""

def ParseCommandLine():
	"""
	Parse the options and arguments given on the command line
	@rtype: boolean
	@returns: TRUE if command line arguments were parsed successfully
	"""

	global gInputPath, gOutputPath, gTorrentGen
	global gBaseURL, gRSSTitle, gRSSDesc, gTorrentTracker
	global gRecurse, gIncludeFiles, gExcludeFiles
	global gSort, gSortDir, gLimitFiles

	verbose = None  #boolean flag for printing verbose info

	## Parse options on command line
	try:
		opts, args = getopt.getopt(sys.argv[1:], "hi:o:rt:u:vD:T:S:RL:I:X:")

		# Check parsed options for sanity.  If all is okay, we should
		# popupate most of the global variables.
		for opt, val in opts:
			if opt == '-h':
				#print help info
				usage()
				return False

			elif opt == '-i':
				if os.path.exists(val):
					gInputPath = val
				else:
					print 'Input path does not exist'
					usage()
					return False

			elif opt == '-o':
				if os.path.exists(os.path.dirname(val)):
					if os.path.isdir(val):
						gOutputPath = os.path.join(val,"index.rss")
					else:
						gOutputPath = val
				else:
					print 'Output path does not exist'
					usage()
					return False

			elif opt == '-r':
				gRecurse = True
			
			elif opt == '-v':
				verbose = True

			elif opt == '-u':
				if len(val) > 0:
					gBaseURL = val
				else:
					print 'You must provide a URL with the -u argument'
					usage()
					return False

			elif opt == '-I':
				gIncludeFiles.append(val)

			elif opt == '-X':
				gExcludeFiles.append(val)

			elif opt == '-S':
				if val.upper() in ["SIZE", "MDATE", "CDATE"]:
					gSort = val.upper()
				else:
					print 'Invalid sort type specified'
					usage()
					return False

			elif opt == '-R':
				gSortDir = -1

			elif opt == '-L':
				try:
					gLimitFiles = eval(val)
					if gLimitFiles <= 0:
						print 'You must specify a positive limit with -L'
						usage()
						return False
				except:
					print 'You must specify a positive integer with -L'
					usage()
					return False

			elif opt == '-D':
				if len(val) > 0:
					gRSSDesc = val
				else:
					print 'You must provide a description with -d'
					usage()
					return False
			
			elif opt == '-T':
				if len(val) > 0:
					gRSSTitle = val
				else:
					print 'You must provide a title with the -t argument'
					usage()
					return False

	except getopt.GetoptError, err:
		print 'Option parsing error: ' + str(err)
		usage()
		return False

	## Given parsed options, infer defaults, etc.
	if not gInputPath:
		usage()
		return False

	if not gOutputPath:
		gOutputPath = os.path.join(gInputPath, 'index.rss')

	if not gBaseURL:
		usage()
		return False

	if not gRSSTitle:
		gRSSTitle = "RSS Directory Index: " + gBaseURL

	if not gRSSDesc:
		gRSSDesc = gRSSTitle

	if verbose:
		print 'input path: ', gInputPath
		print 'output path: ', gOutputPath
		print 'base url: ', gBaseURL
		print 'recursion: ', gRecurse
		print 'include files: ', gIncludeFiles
		print 'exclude files: ', gExcludeFiles
		print 'sort files: ', gSort
		print 'sort direction: ', gSortDir
		print 'limit files: ', gLimitFiles

	return True


def MatchFilename(filename, include, exclude):
	"""
	Matches filename against include / exclude list using path module's 
	fnmatch function. Supports wildcards within each list. Processes include 
	list first, then exclude list. If no include list is specified, all files
	are included (unless excluded).
	
	@type filename: path
	@param filename: filename to match
	
	@type include: array
	@param include: array of filenames to include - wildcards supported
	
	@type exclude: array
	@param exclude: array of filenames to exclude - wildcards supported
	
	@rtype: boolean
	@return: returns TRUE if filename matches, FALSE if not
	"""
	
	match = False

	if len(include) == 0:
		match = True
	else:
		for i in include:
			# print filename, i, fnmatch(filename,i) 
			if fnmatch(filename.name, i) == True or \
			   fnmatch(filename,i) == True:
					match = True

	for x in exclude:
		# print filename, x, fnmatch(filename,x) 
		if fnmatch(filename.name, x) == True or \
				fnmatch(filename, x) == True:
			match = False
		
	if match == True:
		return True

def CreateFileList(oFile):
	"""
	Creates a list of files by walking the directory tree. The list is sorted
	based on the gSort global variable by NAME, SIZE, MDATE or CDATE. If
	gRecurse is TRUE, the directory listing will include all recursive subdirs
	
	@type oFile: path
	@param oFile: directory path from which to list files
	
	Global Variables:
		- L{gRecurse}: if TRUE, lists files within subdirectories
		- L{gSort}:
			- NAME: files are sorted by name
			- SIZE: files are sorted by size
			- MDATE: files are sorted by modified date
			- CDATE: files are sorted by creation date
			- unspecified: files are not sorted (directory listing order)
		- L{gSortDir}: sort direction (-1 for reverse sort)
	
	@rtype: array
	@return: sorted array of paths for filenames within directory tree
	
	"""

	if not oFile:
		return False

	if gRecurse == True:
		fileList = oFile.walkfiles()
	else:
		fileList = oFile.listdir()
	
	if not gSort:
		return [(0, file) for file in fileList]
	
	if gSort == "NAME":
		sortList = [(file.name, file) for file in fileList]
	elif gSort == "SIZE":
		sortList = [(file.size, file) for file in fileList]
	elif gSort == "MDATE":
		sortList = [(file.mtime, file) for file in fileList]
	elif gSort == "CDATE":
		sortList = [(file.ctime, file) for file in fileList]
	
	if sortList:
		sortList.sort()
		if gSortDir == -1:
			sortList.reverse()
		return sortList
	else:
		return False

def CreateRSS():
	"""
	Create RSS file based on directory contents, using L{PyRSS2Gen} module.
	
		- Iterates through file list returned by L{CreateFileList} function
		
		- Checks filename against -I arguments in L{gIncludeFiles} 
			and -X arguments in L{gExcludeFiles}
		
		- Creates L{PyRSS2Gen.RSSItem} for each file, with modification date, 
			size, and zip file contents (if applicable) within description.
		
		- Utilizes L{PyRSS2Gen.RSS2} to generate RSS 2.0 XML file
	
	@rtype: boolean
	@return: TRUE if XML file is created successfully
	"""

	#print "checking for files in " + gInputPath

	rssItems = []

	oFile = path(gInputPath)
	fileList = CreateFileList(oFile)

	for file in fileList:
		item = file[1]
		itemPath = item.abspath()
		relPath = oFile.relpathto(item.abspath())
		sRelPath = relPath.replace("\\","/")
		modTime = datetime.datetime.fromtimestamp(item.mtime)

		desc = "size: " + str(item.size) + " bytes (about "
		desc += str(item.size/(1024*1024))+" mb)"

		if zipfile.is_zipfile(itemPath):
			zf = zipfile.ZipFile(itemPath, 'r')
			desc += "<br/>contents:<br/>"
			desc += str(zf.namelist())

		if os.path.isfile(itemPath) == True and \
				MatchFilename(relPath, gIncludeFiles, gExcludeFiles):
			mime = mimetypes.guess_type(itemPath)[0]
			if not mime:
				mime = "application/x-unknonwn"
			rssItems.append(
				PyRSS2Gen.RSSItem(
					title = sRelPath,
					link = gBaseURL + '/' + sRelPath,
					description = desc,
					enclosure = PyRSS2Gen.Enclosure(
						gBaseURL + '/' + sRelPath,
						item.size,
						mime),
					guid = PyRSS2Gen.Guid(gBaseURL + sRelPath),
					pubDate = modTime)
				)
				
		if len(rssItems) >= gLimitFiles:
			break

	rss = PyRSS2Gen.RSS2(
		title = gRSSTitle,
		link = gBaseURL,
		description = gRSSDesc,
		lastBuildDate = datetime.datetime.now(),
		items = rssItems)
	
	try:
		rss.write_xml(open(gOutputPath, "w"))
		print 'Successfully wrote RSS index to', gOutputPath
	except:
		print 'Unable to write XML file'
		return False

	return True


def main():
	"""
	Main program execution function. Parses commandline and creates RSS file
	@rtype: boolean
	@return: TRUE if program executes successfully and RSS file is created
	"""
	
	return ParseCommandLine() and CreateRSS()

if __name__ == "__main__": main()
